﻿//==============================================================================
// File: ActionRegistry.cs
// Created: 2010-04-30
// Author: Piotr Włodek
//==============================================================================
// This file is a part of MAMMOTH project.
// Copyright (C) 2010 AGH University of Science and Technology, Krakow.
// https://caribou.iisg.agh.edu.pl/trac/mammoth
//
// Mammoth is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// Mammoth is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public License
// along with Mammoth. If not, see http://www.gnu.org/licenses/.
//==============================================================================

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Windows;
using System.Windows.Input;
using WpfRcp.Core.Common;
using WpfRcp.Core.Common.Extensions;
using WpfRcp.Core.Services.Commands;
using Microsoft.Practices.Composite.Regions;

namespace WpfRcp.Services.Commands
{
    public class ActionRegistry : IActionRegistry
    {
        private readonly ICommandCategoryCollection m_CommandCategories;
        private readonly ICommandDescriptorCollection m_CommandDescriptors;

        public ICommandCategoryCollection CommandCategories
        {
            get { return m_CommandCategories; }
        }

        public ICommandDescriptorCollection CommandDescriptors
        {
            get { return m_CommandDescriptors; }
        }

        public ActionRegistry(IRegionManager regionManager, IMenuItemFactory factory)
        {
            m_CommandCategories = new CommandCategoryCollection();
            m_CommandDescriptors = new CommandDescriptorCollection();

            m_CommandCategories.CollectionChanged += (sender, e) => m_CommandDescriptors.UpdatePendingItems();
        }

        public ICommandCategory RegisterCommandCategory(string name)
        {
            if (m_CommandCategories.ContainsItemWithName(name))
                return m_CommandCategories[name];

            ICommandCategory category = new CommandCategory(name);
            m_CommandCategories.Add(category);

            return category;
        }

        public ICommandDescriptor RegisterCommand(string name, string categoryName, ICommand command)
        {
            if (m_CommandDescriptors.ContainsItemWithName(name))
                return m_CommandDescriptors[name];

            ICommandCategory category = RegisterCommandCategory(categoryName);

            ICommandDescriptor commandDescriptor = new CommandDescriptor(name, command, category);
            m_CommandDescriptors.Add(commandDescriptor);

            return commandDescriptor;
        }

        private class CommandCategoryCollection : ItemCollectionBase<ICommandCategory>, ICommandCategoryCollection
        {
        }

        private class CommandDescriptorCollection : ItemCollectionBase<ICommandDescriptor>, ICommandDescriptorCollection
        {
        }

        public abstract class ItemCollectionBase<T> : IEnumerable<T>, INotifyCollectionChanged
            where T : class, INameBasedItem
        {
            protected readonly List<T> m_Items;
            protected readonly List<T> m_PendingItems;

            protected ItemCollectionBase()
            {
                m_Items = new List<T>();
                m_PendingItems = new List<T>();
            }

            public IEnumerator<T> GetEnumerator()
            {
                return m_Items.GetEnumerator();
            }

            IEnumerator IEnumerable.GetEnumerator()
            {
                return GetEnumerator();
            }

            public void Add(T item)
            {
                if (ContainsItem(item))
                    return;

                if (RequirementsSatisfied(item))
                {
                    m_Items.Add(item);
                    OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item));
                }
                else
                {
                    m_PendingItems.Add(item);
                }

                UpdatePendingItems();
            }

            public T this[string name] 
            {
                get
                {
                    T item = GetItemByName(name);
                    if (item == null)
                        throw new KeyNotFoundException();

                    return item;
                }
            }

            public void UpdatePendingItems()
            {
                var acceptedItems = new List<T>();
                var pass = true;

                while (pass)
                {
                    var currentPassAcceptedItems = new List<T>();
                    pass = false;

                    foreach (var pendingItems in m_PendingItems)
                    {
                        if (RequirementsSatisfied(pendingItems))
                        {
                            currentPassAcceptedItems.Add(pendingItems);
                            pass = true;
                        }
                    }

                    foreach (var acceptedItem in currentPassAcceptedItems)
                    {
                        m_PendingItems.Remove(acceptedItem);
                        m_Items.Add(acceptedItem);
                    }

                    acceptedItems.AddRange(currentPassAcceptedItems);
                }

                if (acceptedItems.Count > 0)
                {
                    OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, acceptedItems));
                }
            }

            public bool ContainsItem(T item)
            {
                return m_Items.Contains(item);
            }

            public bool ContainsItemWithName(string name)
            {
                return GetItemByName(name) != null;
            }

            public T GetItemByName(string name)
            {
                return m_Items.FirstOrDefault(t => t.Name == name);
            }

            protected virtual bool RequirementsSatisfied(T item)
            {
                if (ContainsItem(item))
                    return false;
                if (ContainsItemWithName(item.Name))
                    return false;

                return true;
            }

            public event NotifyCollectionChangedEventHandler CollectionChanged;

            private void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
            {
                if (CollectionChanged != null)
                    CollectionChanged(this, e);
            }
        }

    }
}